﻿using ImageProcessingLib;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;

/* Урок "Обработка изображений."
 * Все уроки на http://digitalmodels.ru
 * 
 */

namespace ImProcessPerformanceTest
{
    class Program
    {
        /// <summary>
        /// Получение изображения для тестирования.
        /// </summary>
        /// <returns>Bitmap объект.</returns>
        static Bitmap GetTestBitmap()
        {
            return Properties.Resources.shrek; // 1920 x 1080
        }

        /// <summary>
        /// Время выполнения метода в виде строки для вывода на консоль.
        /// </summary>
        /// <param name="ms">Время выполнения метода в миллисекундах.</param>
        /// <returns>String объект.</returns>
        static string GetElapsedTime(int ms)
        {
            string result;

            if (ms > 60000)
                result = string.Format("{0} мин {1} сек", ms / 60000, (ms % 60000) / 1000);
            else if (ms / 1000 > 3)
                result = string.Format("{0} сек {1} мс", ms / 1000, ms % 1000);
            else
                result = string.Format("{0} мс", ms);

            return result;
        }

        /// <summary>
        /// Метод возвращает массив тестовых случаев: метод и параметры для него.
        /// </summary>
        /// <returns>Массив тестовых случаев.</returns>
        static PerformanceTestItem[] GetDefaultPerformanceTestItems()
        {
            PerformanceTestItem[] testItems = new PerformanceTestItem[14];
            int index = 0;

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Brightness",
                Parameters = new object[] { 20, 20, 20 }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Contrast",
                Parameters = new object[] { 1.15f }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Desaturate",
                Parameters = new object[] { }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Saturate",
                Parameters = new object[] { 0.5f }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Blur",
                Parameters = new object[] { 1 }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Diffuse",
                Parameters = new object[] { 1 }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Invert",
                Parameters = new object[] { }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Hue",
                Parameters = new object[] { 120, 140, 192 }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Sepia",
                Parameters = new object[] { }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Contour",
                Parameters = new object[] { }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Relief",
                Parameters = new object[] { Convert.ToSingle(Math.PI / 4) }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Gamma",
                Parameters = new object[] { 0.9f }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Emboss",
                Parameters = new object[] { Math.PI / 4, 4 }
            };

            testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Sharpen",
                Parameters = new object[] { }
            };

            /*testItems[index++] = new PerformanceTestItem()
            {
                MethodName = "Levels",
                Parameters = new object[] { 16 },
                Repeat = 1
            };*/

            return testItems;
        }

        /// <summary>
        /// Получение протокола тестирования.
        /// </summary>
        /// <returns>PerformanceTestProtocol объект.</returns>
        static PerformanceTestProtocol GetPerformanceTestProtocol()
        {
            string fileName = "perf.xml";
            XmlSerializer formatter = new XmlSerializer(typeof(PerformanceTestProtocol));
            PerformanceTestProtocol ptp;

            if (!File.Exists(fileName))
            {
                ptp = new PerformanceTestProtocol
                {
                    PerformanceTestItems = GetDefaultPerformanceTestItems()
                };

                using (Stream fs = new FileStream(fileName, FileMode.Create))
                {
                    formatter.Serialize(fs, ptp);
                }
            }
            else
            {
                using (Stream fs = new FileStream(fileName, FileMode.Open))
                {
                    ptp = (PerformanceTestProtocol)formatter.Deserialize(fs);
                }
            }

            return ptp;
         }

        /// <summary>
        /// Метод загружает результаты тестирования для сравнения с текущим тестированием.
        /// </summary>
        /// <param name="fileName">Имя файла</param>
        /// <returns>Массив PerformanceTestResult объектов.</returns>
        static PerformanceTestResult[] LoadPerfTestResults(string fileName)
        {
            XmlSerializer formatter = new XmlSerializer(typeof(PerformanceTestResult[]));
            PerformanceTestResult[] ptr;

            using (Stream fs = new FileStream(fileName, FileMode.Open))
            {
                ptr = (PerformanceTestResult[])formatter.Deserialize(fs);
            }

            return ptr;
        }

        /// <summary>
        /// Получение имени метода с выравниванием. 
        /// </summary>
        /// <param name="method">MethodInfo объект.</param>
        /// <returns>Выравненное имя метода.</returns>
        static string GetMethodName(MethodInfo method)
        {
            string mn = string.Format("{0}:", method.Name);

            // выравнивание
            while (mn.Length < 8)
                mn += " ";

            return mn;
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Старт тестирования.");            

            // получение изображения для тестирования
            Bitmap bitmap = GetTestBitmap();
            Console.WriteLine(string.Format("Исходное изображение {0} x {1}", bitmap.Width, bitmap.Height));
            Console.WriteLine();

            // получение BitmapInfo4 как первого параметра для всех методов библиотеки. 
            BitmapInfo4 bi = Utils.GetBitmapInfo(bitmap);
            Stopwatch stopWatch = new Stopwatch();
            
            // декларация класса с методами для обработки изображений
            ImageProcessing ip = new ImageProcessing();

            Type typeImProcess = ip.GetType();
            object obj = Activator.CreateInstance(typeImProcess);

            PerformanceTestProtocol perfTestProtocol = GetPerformanceTestProtocol();
            List<PerformanceTestResult> perfResults = new List<PerformanceTestResult>();
            PerformanceTestResult[] perfTestsForComparing = null;

            if (perfTestProtocol.Compare)
            {
                // загрузка результатов тестирования для сравнения с текущим
                if (File.Exists(perfTestProtocol.FileForCompare))
                {
                    perfTestsForComparing = LoadPerfTestResults(perfTestProtocol.FileForCompare);
                }
            }

            // метрика для замера производительности
            IMetric performanceMetric = new MininmumMetric();

            // цикл по всем методам в ImageProcessing
            foreach (MethodInfo method in typeImProcess.GetMethods())
            {
                MethodInfo mi = typeImProcess.GetMethod(method.Name);
                PerformanceTestItem testItem = perfTestProtocol.PerformanceTestItems.FirstOrDefault(x => x.MethodName == method.Name);

                if (testItem == null)
                    continue;

                // тесты со занчением флага Enable == false пропускаем.
                if (!testItem.Enable)
                    continue;

                ParameterInfo[] pi = method.GetParameters();
                object[] parameters = new object[pi.Length];

                // первый параметр всегда BitmapInfo4
                parameters[0] = bi;

                // копируем параметры загруженные из XML файла.
                Array.Copy(testItem.Parameters, 0, parameters, 1, parameters.Length - 1);

                int[] ms = new int[testItem.Repeat];

                for (int i = 0; i < testItem.Repeat; i++)
                {
                    // сбрасываем показания таймера
                    stopWatch.Reset();

                    // запускаем таймер
                    stopWatch.Start();

                    // вызов метода класса ImageProcessing 
                    object biref = mi.Invoke(obj, parameters);

                    // останавливаем таймер
                    stopWatch.Stop();

                    // фиксируем время прохождения теста.
                    ms[i] = Convert.ToInt32(stopWatch.Elapsed.TotalMilliseconds);
                }

                // получение результата тестирования
                int metric = performanceMetric.GetMetric(ms.ToList());

                // формируем строку для вывода на консоль
                string infoString = GetElapsedTime(metric);

                // если perfTestsForComparing не null значит сравниваем с другим тестированием.
                if (perfTestsForComparing != null)
                {
                    PerformanceTestResult pti = perfTestsForComparing.FirstOrDefault(x => x.MethodName == method.Name);

                    if (pti != null)
                    {
                        int totalMilliseconds = pti.TotalMilliseconds;
                        float percent = (totalMilliseconds - metric) / Convert.ToSingle(totalMilliseconds) * 100.0f;

                        if (Math.Abs(percent) > perfTestProtocol.Tolerance)
                        {
                            string compareInfo = (percent < 0) ? "\tдеградация" : "\tускорение";
                            compareInfo += string.Format(" {0}%", Math.Abs(Math.Round(percent, 1)));
                            infoString += compareInfo;
                        }
                    }
                }

                // вывод результата на консоль
                Console.WriteLine(string.Format("{0}\t{1}", GetMethodName(method), infoString));

                if (perfTestProtocol.SaveResults)
                {
                    PerformanceTestResult ptr = new PerformanceTestResult
                    {
                        MethodName = method.Name,
                        Parameters = testItem.Parameters,
                        TotalMilliseconds = metric
                    };

                    perfResults.Add(ptr);
                }
            }

            // сохранение результатов
            if (perfTestProtocol.SaveResults)
            {
                DateTime dt = DateTime.Now;
                string fileName = $"{dt.Year}{dt.Month}{dt.Day}{dt.Hour}{dt.Minute}{dt.Second}.xml";
                XmlSerializer formatter = new XmlSerializer(typeof(PerformanceTestResult[]));               

                using (Stream fs = new FileStream(fileName, FileMode.Create))
                {
                    formatter.Serialize(fs, perfResults.ToArray());
                }
            }

            // завершение тестирования
            Console.WriteLine();
            Console.WriteLine("Тестирование завершено. Нажмите Enter для выхода.");
            Console.ReadLine();
        }
    }

    [Serializable]
    public class PerformanceTestBase
    {
        public string MethodName { get; set; }
        public object[] Parameters { get; set; }
    }

    [Serializable]
    public class PerformanceTestItem : PerformanceTestBase
    {
        public bool Enable { get; set; } = true;
        public int Repeat { get; set; } = 20;
    }

    [Serializable]
    public class PerformanceTestResult : PerformanceTestBase
    {
        public int TotalMilliseconds { get; set; }
    }

    [Serializable]
    public class PerformanceTestProtocol
    {
        public bool SaveResults { get; set; } = true;
        public bool Compare { get; set; } = false;
        public string FileForCompare { get; set; }
        public float Tolerance { get; set; } = 5.0f;
        public PerformanceTestItem[] PerformanceTestItems { get; set; }
    }
}
